package exchange;

import java.util.LinkedList;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;

import message.Message;
import message.Order;

public class ExchangeEngine implements Runnable {
	private String symbol;
	private TreeMap<Double, LinkedList<Order>> sellStack = new TreeMap<>();
	private TreeMap<Double, LinkedList<Order>> buyStack = new TreeMap<>(
			new InverseDoubleComparator());
	public BlockingQueue<Message> outQueue = new LinkedBlockingDeque<>();
	public BlockingQueue<Order> inQueue = new LinkedBlockingDeque<>();

	public ExchangeEngine(String symbol) {
		super();
		this.symbol = symbol;
	}

	public String getSymbol() {
		return symbol;
	}

	public void addOrder(Order o) {
		double price = o.getPrice();
		LinkedList<Order> list;

		if (o.getOperation() == 1) {
			if (buyStack.containsKey(price)) {
				list = buyStack.get(price);
			} else {
				list = new LinkedList<>();
				buyStack.put(o.getPrice(), list);
			}
			list.add(o);
		} else if (o.getOperation() == 2) {
			if (sellStack.containsKey(price)) {
				list = sellStack.get(price);
			} else {
				list = new LinkedList<>();
				sellStack.put(o.getPrice(), list);
			}
			list.add(o);
		} else
			System.out.println("ERROR: Order " + o.getClientId()
					+ " has wrong operation. Ignored.");
	}

	private boolean matchOrder(Order o) {
		double price = o.getPrice();
		TreeMap<Double, LinkedList<Order>> oppositeMap;

		if (o.getOperation() == 1) {
			oppositeMap = sellStack;
			if (oppositeMap.isEmpty())
				return false;
			double oppositePrice = oppositeMap.firstKey();

			if (price >= oppositePrice) {
				processMatching(o, oppositeMap, oppositePrice);
				return true; // fill success
			} else {
				System.out.println("NOFILL:" + o);
				return false;
			}

		} else if (o.getOperation() == 2) {
			oppositeMap = buyStack;
			if (oppositeMap.isEmpty())
				return false;
			double oppositePrice = oppositeMap.firstKey();

			if (price <= oppositePrice) {
				processMatching(o, oppositeMap, oppositePrice);
				return true; // fill success
			} else {
				System.out.println("NOFILL:" + o);
				return false;
			}
		} else {
			System.out.println("ERROR: Order " + o.getClientId());
			return false;
		}
	}

	private void processMatching(Order o,
			TreeMap<Double, LinkedList<Order>> oppositeMap, double oppositePrice) {
		LinkedList<Order> list;
		list = oppositeMap.get(oppositePrice);
		Order oppositeOrder = list.getFirst();
		System.out.println("FILL:" + o + " : " + oppositeOrder);
		matchBoth(o, oppositeOrder);
		if (oppositeOrder.getLeavesQty() == 0)
			removeFromStack(oppositeOrder, oppositeMap);
	}

	private void removeFromStack(Order o, TreeMap<Double, LinkedList<Order>> map) {
		double price = o.getPrice();
		LinkedList<Order> list = map.get(price);
		list.removeFirst();
		if (list.isEmpty()) {
			map.remove(price);
		}
	}

	public void trade(Order o1) {
		o1.sendAck(outQueue); 
		while (o1.getLeavesQty() > 0) {
			if (matchOrder(o1))
				continue;
			System.out.println("Place: " + o1);
			addOrder(o1);
			break;
		}
	}

	private void matchBoth(Order newOrder, Order oldOrder) {
		int newQty = newOrder.getLeavesQty();
		int oldQty = oldOrder.getLeavesQty();
		int fillQty = Math.min(newQty, oldQty);
		double fillPx = oldOrder.getPrice();
		newOrder.fill(fillQty, fillPx, outQueue);
		oldOrder.fill(fillQty, fillPx, outQueue);
	}

	public static void main(String[] args) {
		ExchangeEngine ee = new ExchangeEngine("AAPL");

		ee.trade(new Order("client1", "order01", "AAPL", 1, 100.5, 20));
		ee.trade(new Order("client1", "order02", "AAPL", 2, 100.3, 30));
		ee.trade(new Order("client2", "order03", "AAPL", 1, 100.4, 80));
		ee.trade(new Order("client2", "order04", "AAPL", 2, 100.7, 100));
		ee.trade(new Order("client2", "order05", "AAPL", 2, 100.4, 150));
		ee.trade(new Order("client2", "order06", "AAPL", 2, 100.6, 110));
		ee.trade(new Order("client3", "order07", "AAPL", 2, 100.6, 80));
		ee.trade(new Order("client3", "order08", "AAPL", 1, 100.9, 290));
		ee.printEngine();
	}

	public void printEngine() {
		System.out.println("sellSet:");
		printMap(sellStack);
		System.out.println("buySet:");
		printMap(buyStack);
	}

	public void printQueue(LinkedList<Order> l) {
		// System.out.print(l.getFirst());
		for (Order order : l) {
			System.out.print(order + " ");
		}
	}

	public void printMap(TreeMap<Double, LinkedList<Order>> map) {
		Set<Double> keys = map.keySet();

		for (Double k : keys) {
			System.out.print("" + k + "=[");
			printQueue(map.get(k));
			System.out.println("] ");
		}
		// System.out.println("");
	}

	@Override
	public void run() {
		try {
			while (true) {
				Order o = inQueue.take();
				trade(o);
			}

		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}